﻿
package extremefx.anim.transitions {

	/**
	 * @author Marcelo Volmaro
	 */
	public class CubicBezierTransition implements ITransition {
		private var _p1x:Number;
		private var _p1y:Number;
		private var _p2x:Number;
		private var _p2y:Number;
		
		public static function get EASE_IN_OUT():CubicBezierTransition{
			return new CubicBezierTransition(0.42, 0, 0.58, 1);
		}
		
		public static function get EASE_IN():CubicBezierTransition{
			return new CubicBezierTransition(0.42, 0, 1, 1);
		}
		
		public static function get EASE_OUT():CubicBezierTransition{
			return new CubicBezierTransition(0, 0, 0.58, 1);
		}
		
		public static function get EASE_OUT_IN():CubicBezierTransition{
			return new CubicBezierTransition(0, 0.42, 1, 0.58);
		}

		public function CubicBezierTransition(p1X:Number = 0.25, p1Y:Number = 0.1, p2X:Number = 0.25, p2Y:Number = 1){
			_p1x = p1X;
			_p1y = p1Y;
			_p2x = p2X;
			_p2y = p2Y;
		}
		
		public function step(pCurrent:Number, pStart:Number, pMaxDistance:Number, pEnd:Number):Number {
			var ax:Number=0,
				bx:Number=0,
				cx:Number=0,
				ay:Number=0,
				by:Number=0,
				cy:Number=0;
				
			// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
			cx = 3 * _p1x; 
			bx = 3 * (_p2x - _p1x) - cx; 
			ax = 1 - cx - bx; 
			cy = 3 * _p1y; 
			by = 3 * (_p2y - _p1y) - cy; 
			ay = 1 - cy - by;
			// Convert from input time to parametric value in curve, then from that to output time.
    		return _sampleCurveY(_solveCurveX(pCurrent, 1 / (200 * pMaxDistance), ax, bx, cx), ay, by, cy);
		}
		
		private function _sampleCurveX(t:Number, ax:Number, bx:Number, cx:Number):Number {
			return ((ax * t + bx) * t + cx ) * t;
		}
		
        private function _sampleCurveY(t:Number, ay:Number, by:Number, cy:Number):Number {
        	return ((ay * t + by) * t + cy) * t;
        }
        
        private function _sampleCurveDerivativeX(t:Number, ax:Number, bx:Number, cx:Number):Number {
        	return (3 * ax * t + 2 * bx) * t + cx;
        }
        
        // Given an x value, find a parametric value it came from.
        private function _solveCurveX(pX:Number, pEpsilon:Number, ax:Number, bx:Number, cx:Number):Number {
        	var t0:Number,t1:Number,t2:Number,x2:Number,d2:Number,i:Number;
			
            // First try a few iterations of Newton's method -- normally very fast.
            for(t2 = pX, i = 0; i < 8; ++i) {
            	
            	x2 = _sampleCurveX(t2, ax, bx, cx) - pX;
            	if (_fabs(x2) < pEpsilon) return t2;
            	
            	d2 = _sampleCurveDerivativeX(t2, ax, bx, cx); 
            	if (_fabs(d2) < 1e-6) break;
				
				t2 = t2 - x2 / d2;
            }
            
            // Fall back to the bisection method for reliability.
            t0 = 0; 
            t1 = 1; 
            t2 = pX; 
            
            if(t2 < t0) return t0;
            if(t2 > t1) return t1;
            
            while(t0 < t1) {
            	x2 = _sampleCurveX(t2, ax, bx, cx);
            	
            	if(_fabs(x2 - pX) < pEpsilon) return t2;
            	
            	if(pX > x2) {
            		t0 = t2;
            		
            	} else {
            		t1 = t2;
            	}
            	
            	t2 = (t1 - t0) * .5 + t0;
            }
            	
            return t2; // Failure.
        }
        
        private function _fabs(n:Number):Number {
        	return n >= 0 ? n : -n;  
        } 
	}
}
